/*
 * COPYRIGHT (C) 2018, Real-Thread Information Technology Ltd
 * 
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-12-19     heyuanjie87  first version.
 */

#include <rtthread.h>
#include <rtdevice.h>

#include "videolib.h"
#include <dfs_poll.h>

static void bufin_complete(struct camif_buf *buf, int status)
{
    buf->status = status;
    rt_list_remove(&buf->node);
    rt_list_insert_before(&buf->vid->vbufs.out_que, &buf->node);

    rt_wqueue_wakeup(&buf->vid->wq_in, (void*)POLLIN);
}

static struct camif_buf* _buf_new(uint32_t length)
{
    struct camif_buf *buf;

    buf = rt_calloc(sizeof(struct camif_buf), 1);
    if (buf)
    {
        rt_list_init(&buf->node);
        buf->bufsz = length;
        buf->data = rt_malloc_align(length, 4);
        if (!buf->data)
        {
            rt_free(buf);
            buf = 0;
        }
    }

    return buf;
}

static void _buf_delete(struct camif_buf *buf)
{
    rt_list_remove(&buf->node);
    rt_free(buf->data);
    rt_free(buf);
}

static int _buf_active(rt_video_t *vid)
{
    struct camif_buf *buf;

    if (rt_list_isempty(&vid->vbufs.in_que))
        return -EINVAL;

    buf = rt_list_first_entry(&vid->vbufs.in_que, struct camif_buf, node);
    vid->ci->active = buf;
    vid->ci->ops->buf_active(vid->ci, buf);

    return 0;
}

static int _qbuf(rt_video_t *vid, uint16_t index)
{
    struct camif_buf *buf;

    if (index >= vid->vbufs.count)
        return -EINVAL;

    buf = vid->vbufs.alloced[index];

    rt_list_insert_after(&vid->vbufs.in_que, &buf->node);

    return 0;
}

static int _waitfor_readyread(rt_video_t *vid, int nonblock)
{
    int ret = 0;

    if (rt_list_isempty(&vid->vbufs.out_que))
    {
        if (nonblock)
            return -EAGAIN;

        ret = rt_wqueue_wait(&vid->wq_in, 0, -1);
    }

    return ret;
}

static int _buf_read(rt_video_t *vid, void *buf, size_t size)
{
    struct camif_buf *ib;

    if (rt_list_isempty(&vid->vbufs.out_que))
        return 0;

    ib = rt_list_first_entry(&vid->vbufs.out_que, struct camif_buf, node);

    if (size > ib->bufsz)
        size = ib->bufsz;

    rt_memcpy(buf, ib->data, size);

    return size;
}

static int _format_get(rt_video_t *vid, struct video_format *fmt, enum video_type type)
{
    fmt->type = type;

    if (!vid->ops->get_format)
        return -EIO;

    if (vid->ops->get_format(vid, fmt) != 0)
        return -EIO;

    return 0;
}

static int _format_init(rt_video_t *vid)
{
    struct video_format fmt;
    int ret;

    ret = _format_get(vid, &fmt, VIDEO_TYPE_CAPTURE);
    if (ret != 0)
        goto _out;

    ret = vid->ci->ops->set_format(vid->ci, &fmt);

_out:
    return ret;
}

static int _stream_close(rt_video_t *vid)
{
    if (vid->ops->stream_enable)
        vid->ops->stream_enable(vid, 0);

    vid->ci->ops->stream_stop(vid->ci);

    if (vid->ops->deinit)
        vid->ops->deinit(vid);

    return 0;
}

static int _vbufs_clear(rt_video_t *vid)
{
    int i;
    struct vbuf_container *vb = &vid->vbufs;

    if ((vb->memtype != VIDEO_MEMORY_MMAP) || (vb->count == 0))
        return 0;

    for (i = 0; i < vb->count; i ++)
    {
        _buf_delete(vb->alloced[i]);
        vb->alloced[i] = 0;
    }
    vb->count = 0;

    return 0;
}

int rt_video_open(rt_video_t *vid)
{
    int ret = 0;

    if (vid->parent.ref_count == 0)
    {
        rt_memset(&vid->vbufs, 0, sizeof(vid->vbufs));
        rt_list_init(&vid->vbufs.in_que);
        rt_list_init(&vid->vbufs.out_que);
        rt_wqueue_init(&vid->wq_in);
        ret = vid->ci->ops->init(vid->ci);
        ret = vid->ops->init(vid);
        ret = _format_init(vid);
    }
    vid->parent.ref_count ++;

    return ret;
}

int rt_video_close(rt_video_t *vid)
{
    if (vid->parent.ref_count == 1)
    {
        _stream_close(vid);
        _vbufs_clear(vid);
    }
    vid->parent.ref_count --;

    return 0;
}

int rt_video_reqbufs(rt_video_t *vid, struct video_requestbuffers *req)
{
    struct camif_buf *buf;
    int i;
    struct video_format fmt;

    if (!vid || !req)
        return -EINVAL;

    if (_format_get(vid, &fmt, VIDEO_TYPE_CAPTURE) != 0)
        return -EIO;

    for (i = 0; i < req->count; i ++)
    {
        buf = _buf_new(fmt.fmt.pix.height * fmt.fmt.pix.bytesperline);
        if (!buf)
            break;

        vid->vbufs.alloced[i] = buf;

        buf->complete = bufin_complete;
        buf->vid = vid;
    }
    vid->vbufs.count = i;

    return 0;
}

int rt_video_querybuf(rt_video_t *vid, struct video_buffer *vb)
{
    struct camif_buf *buf;

    if (vb->index >= vid->vbufs.count)
        return -EINVAL;

    buf = vid->vbufs.alloced[vb->index];
    vb->m.userptr = buf->data;
    vb->length = buf->bufsz;

    return 0;
}

int rt_video_qbuf(rt_video_t *vid, struct video_buffer *vb)
{
    int ret;

    ret = _qbuf(vid, vb->index);

    return ret;
}

int rt_video_streamon(rt_video_t *vid)
{
    int ret;

    ret = _buf_active(vid);
    if (ret == 0)
    {
        ret = vid->ci->ops->stream_start(vid->ci);
    }

    return ret;
}

void rt_camif_complete(rt_camif_t *ci, int status)
{
    struct camif_buf *buf = ci->active;

    ci->active = 0;
    if (buf)
    {
        buf->complete(buf, status);
        _buf_active(buf->vid);
    }
}

int rt_video_read(rt_video_t *vid, void *buf, size_t size, int nonblock)
{
    int ret;

    if (vid->vbufs.count == 0)
    {
        struct video_requestbuffers req;

        req.count = 1;

        ret = rt_video_reqbufs(vid, &req);
        if (ret != 0)
            goto _out;
    }

    _qbuf(vid, 0);
    rt_video_streamon(vid);

    ret = _waitfor_readyread(vid, nonblock);
    if (ret != 0)
        goto _out;

    ret = _buf_read(vid, buf, size);

_out:
    return ret;
}

int rt_video_s_fmt(rt_video_t *vid, struct video_format *fmt)
{
    int ret;

    if (!vid || !fmt)
        return -EINVAL;

    ret = vid->ops->set_format(vid, fmt);
    if (ret != 0)
        goto _out;

    ret = vid->ci->ops->set_format(vid->ci, fmt);

_out:
    return ret;
}

rt_camif_t* rt_camif_get(const char *name)
{
    struct rt_device *dev;

    dev = rt_device_find(name);
    if (dev->ref_count == 0)
    {
       rt_camif_t *ci = (rt_camif_t*)dev;

       ci->ops->init(ci);
       dev->ref_count ++;
    }

    return (rt_camif_t*)dev; 
}

int rt_camif_register(rt_camif_t *ci, const char *name, void *userdata)
{
    ci->parent.user_data = userdata;
    ci->active = 0;

    return rt_device_register(&ci->parent, name, 0);
}

int rt_camif_reg_write(rt_camif_t *ci, uint8_t slv, uint16_t reg, uint8_t *data)
{
    int reglen = 8;

    if (!ci->ops || !ci->ops->sccb_write)
        return -ENOSYS;

    if (reg & 0xff00)
        reglen = 16;

    return ci->ops->sccb_write(ci, slv, reg, reglen, data);
}

int rt_camif_reg_read(rt_camif_t *ci, uint8_t slv, uint16_t reg, uint8_t *data)
{
    int reglen = 8;

    if (!ci->ops || !ci->ops->sccb_read)
        return -ENOSYS;

    if (reg & 0xff00)
        reglen = 16;

    return ci->ops->sccb_read(ci, slv, reg, reglen, data);
}

int rt_camif_set_xclk(rt_camif_t *ci, uint32_t freq)
{
    int ret;

    if (!ci || !ci->ops || !ci->ops->ioctl)
        return -ENOSYS;

    ret = ci->ops->ioctl(ci, VIDIOC_SET_XCLK, freq);

    return ret;
}

int rt_camif_power_reset(rt_camif_t *ci, int pwdn_lv, int reset_lv)
{
    ci->ops->ioctl(ci, VIDIOC_PWDN, pwdn_lv);
    rt_thread_mdelay(20);
    ci->ops->ioctl(ci, VIDIOC_RESET, pwdn_lv);
    rt_thread_mdelay(20);
    ci->ops->ioctl(ci, VIDIOC_RESET, !pwdn_lv);
    rt_thread_mdelay(20);

    return 0;
}
